ADVERTISEMENT
Responsive Ad Unit • Modern C++ Toolchains & Compilers
🎯 Core Learning Objectives
  • Master ISO C++ standards, compiler mechanics, and idioms (C++11/14/17/20).
  • Implement memory-safe patterns, RAII, dynamic polymorphism, and generic templates.
  • Analyze assembly output, cache efficiency, and move semantics.
  • Demonstrate conceptual mastery via the complete 12-question technical assessment.

6.1. Multithreading in C++ Programming

Multithreading is a technique in programming that allows multiple threads to run concurrently within a program. A thread is the smallest unit of execution that can run independently, and multithreading involves executing multiple threads simultaneously to improve the performance of a program, particularly in tasks that are computationally heavy or involve waiting for external resources (like I/O operations).

Key Concepts

  • Thread: A single path of execution in a program.
  • Concurrency: The ability of the system to manage multiple tasks at once, not necessarily simultaneously.
  • Parallelism: The ability to execute multiple tasks at exactly the same time, often on different CPU cores.

In C++, multithreading can be implemented using the Standard Library support provided by the <thread> header, which was introduced in C++11.

Steps to Create a Thread in C++

Include the thread header:

C++ Source
#include <thread>
  • Create a thread: To create a thread, you instantiate an object of the std::thread class, passing a function or a callable object (such as a lambda function or functor) that will be executed by the thread.

Example 1: Basic Multithreading

This example demonstrates creating a thread that executes a simple function.

C++ Source
#include <iostream>
#include <thread>
 
void printHello() {
    std::cout << "Hello from the thread!" << std::endl;
}
 
int main() {
    // Create a thread and pass the function to it
    std::thread t(printHello);
 
    // Wait for the thread to finish before the program exits
    t.join();
 
    std::cout << "Main thread finished" << std::endl;
    return 0;
}

Explanation:

  • std::thread t(printHello) creates a new thread t that starts executing the printHello function.
  • t.join() blocks the main thread until the thread t finishes its execution.

Important: The join() function is necessary to wait for the thread to finish its work before the main program ends. Without join(), the main thread might finish before the new thread completes, leading to undefined behavior.

Example 2: Multithreading with Lambda Functions

You can also pass lambda functions to threads, which provides more flexibility and ease in defining thread behavior inline.

C++ Source
#include <iostream>
#include <thread>
 
int main() {
    // Create a thread using a lambda function
    std::thread t([](){
        std::cout << "Hello from the lambda thread!" << std::endl;
    });
 
    // Wait for the thread to finish
    t.join();
 
    std::cout << "Main thread finished" << std::endl;
    return 0;
}

Explanation:

  • A lambda function is passed directly to std::thread. The lambda function will be executed in the newly created thread.
  • The join() method ensures the main thread waits for the lambda thread to finish.

Example 3: Multiple Threads

In this example, we create multiple threads that perform different tasks concurrently.

C++ Source
#include <iostream>
#include <thread>
 
void task1() {
    std::cout << "Task 1 is executing" << std::endl;
}
 
void task2() {
    std::cout << "Task 2 is executing" << std::endl;
}
 
int main() {
    // Create two threads
    std::thread t1(task1);
    std::thread t2(task2);
 
    // Wait for both threads to finish
    t1.join();
    t2.join();
 
    std::cout << "Both tasks finished" << std::endl;
    return 0;
}

Explanation:

  • Two threads (t1 and t2) are created and execute task1() and task2() concurrently.
  • join() is used to ensure both threads finish before the main program ends.

Example 4: Passing Arguments to Threads

Threads can be passed arguments just like any other function in C++.

C++ Source
#include <iostream>
#include <thread>
 
void printSum(int a, int b) {
    std::cout << "Sum: " << a + b << std::endl;
}
 
int main() {
    int x = 5, y = 10;
    // Pass arguments to the thread
    std::thread t(printSum, x, y);
 
    // Wait for the thread to finish
    t.join();
 
    return 0;
}

Explanation:

  • The printSum function takes two integers as arguments. These arguments are passed to the thread when it's created.

Example 5: Thread Safety and Synchronization

When multiple threads access shared resources concurrently, we must ensure thread safety to avoid race conditions (when two threads access the same data at the same time, leading to unpredictable results). C++ provides several synchronization mechanisms, such as std::mutex for mutual exclusion.

C++ Source
#include <iostream>
#include <thread>
#include <mutex>
 
std::mutex mtx; // Declare a mutex
 
void printNumber(int num) {
    mtx.lock(); // Lock the mutex
    std::cout << "Number: " << num << std::endl;
    mtx.unlock(); // Unlock the mutex
}
 
int main() {
    std::thread t1(printNumber, 1);
    std::thread t2(printNumber, 2);
 
    t1.join();
    t2.join();
 
    return 0;
}

Explanation:

  • std::mutex mtx is used to prevent multiple threads from printing to std::cout at the same time, which could cause garbled output.
  • lock() is used to acquire the mutex, and unlock() releases it after the thread has finished using the shared resource.

Example 6: Thread Pool (Advanced)

A thread pool is a collection of threads that can be used to execute a large number of tasks, avoiding the overhead of creating and destroying threads for each task. C++ does not provide a built-in thread pool, but it can be implemented using std::thread and other utilities like std::queue, std::mutex, and std::condition_variable.

Example 7: Using std::async for Asynchronous Tasks

Instead of manually managing threads, you can use std::async (available from C++11) to run functions asynchronously.

C++ Source
#include <iostream>
#include <future>
 
int calculateSum(int a, int b) {
    return a + b;
}
 
int main() {
    // Launch an asynchronous task
    std::future<int> result = std::async(calculateSum, 5, 7);
 
    // Wait for the result and print it
    std::cout << "Sum: " << result.get() << std::endl;
 
    return 0;
}

Explanation:

  • std::async runs calculateSum asynchronously and returns a std::future object, which can later be used to get the result of the computation once it is finished.

Conclusion

Multithreading in C++ allows you to efficiently perform multiple operations in parallel, improving performance, especially in CPU-bound or I/O-bound tasks. It is crucial to manage synchronization when multiple threads interact with shared resources to avoid data races and undefined behavior.

Key components for multithreading in C++ include:

  • std::thread for creating and managing threads.
  • std::mutex and std::lock_guard for synchronization.
  • std::async for simpler asynchronous execution.

By mastering these tools, you can harness the full potential of modern multicore processors and design high-performance, concurrent C++ applications.

6.2. Smart Pointers

In C++, smart pointers are wrappers around regular pointers that provide automatic memory management. They help ensure that dynamically allocated memory is correctly freed when it is no longer needed, preventing memory leaks and dangling pointers.

1. Why Smart Pointers?

In C++, memory is allocated dynamically using new and freed using delete. However, this manual management of memory can be error-prone. It is easy to forget to delete memory or incorrectly manage object lifetimes. Smart pointers automate this process, ensuring that memory is freed when it is no longer in use.

They are part of the C++11 standard and later versions and are defined in the <memory> header.

2. Types of Smart Pointers

There are three main types of smart pointers in C++:

  • std::unique_ptr:
    • It is the simplest and most restrictive type of smart pointer.
    • A unique_ptr owns a resource and cannot be copied, only moved.
    • When the unique_ptr goes out of scope, the resource it owns is automatically deleted.
    • Ensures that there is only one owner of the resource, which makes it ideal for situations where ownership should not be shared.
  • std::shared_ptr:
    • It allows multiple smart pointers to share ownership of a resource.
    • A shared_ptr maintains a reference count to keep track of how many shared_ptr instances are pointing to the same resource.
    • When the last shared_ptr pointing to the resource is destroyed or reset, the resource is deleted.
    • Suitable for situations where multiple parts of the program need to share ownership of the same resource.
  • std::weak_ptr:
    • A weak_ptr is a non-owning smart pointer that does not affect the reference count.
    • It is used to break circular references between shared_ptr instances.
    • It allows access to an object managed by shared_ptr, but it does not contribute to the reference count, preventing memory leaks in circular references.

3. Example Usage

3.1. std::unique_ptr

A unique_ptr is a smart pointer that ensures that there is only one owner of a dynamically allocated resource. When the unique_ptr goes out of scope, the resource is automatically deallocated.

C++ Source
#include <iostream>
#include <memory>
 
class MyClass {
public:
    MyClass() { std::cout << "MyClass Constructor\n"; }
    ~MyClass() { std::cout << "MyClass Destructor\n"; }
    void greet() { std::cout << "Hello from MyClass!\n"; }
};
 
int main() {
    // Create a unique pointer to MyClass
    std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
    ptr1->greet();
 
    // ptr1 goes out of scope here, and MyClass will be destroyed automatically
    return 0;
}

Output:

C++ Source
MyClass Constructor
Hello from MyClass!
MyClass Destructor

In this example, ptr1 is a unique_ptr to a MyClass object. When the unique_ptr goes out of scope at the end of the main() function, the MyClass object is automatically destroyed.

3.2. std::shared_ptr

A shared_ptr allows multiple smart pointers to share ownership of a resource. The resource is deleted when the last shared_ptr that owns it is destroyed.

C++ Source
#include <iostream>
#include <memory>
 
class MyClass {
public:
    MyClass() { std::cout << "MyClass Constructor\n"; }
    ~MyClass() { std::cout << "MyClass Destructor\n"; }
    void greet() { std::cout << "Hello from MyClass!\n"; }
};
 
int main() {
    // Create a shared pointer to MyClass
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    ptr1->greet();
 
    {
        // Create another shared pointer that shares ownership
        std::shared_ptr<MyClass> ptr2 = ptr1;
        std::cout << "ptr2 is also pointing to the same object\n";
    } // ptr2 goes out of scope here, but ptr1 still holds the resource
 
    // ptr1 goes out of scope here, and MyClass will be destroyed automatically
    return 0;
}

Output:

C++ Source
MyClass Constructor
Hello from MyClass!
ptr2 is also pointing to the same object
MyClass Destructor

In this example:

  • ptr1 is a shared_ptr that owns a MyClass object.
  • ptr2 is another shared_ptr that shares ownership of the same MyClass object. The object is not deleted until the last shared_ptr (ptr1 in this case) goes out of scope.

3.3. std::weak_ptr

A weak_ptr is used to observe a resource managed by a shared_ptr without taking ownership. It is useful for breaking circular references between shared_ptr instances.

C++ Source
#include <iostream>
#include <memory>
 
class MyClass {
public:
    MyClass() { std::cout << "MyClass Constructor\n"; }
    ~MyClass() { std::cout << "MyClass Destructor\n"; }
    void greet() { std::cout << "Hello from MyClass!\n"; }
};
 
int main() {
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
 
    // Create a weak pointer that observes the shared pointer
    std::weak_ptr<MyClass> weakPtr = ptr1;
 
    if (auto ptr2 = weakPtr.lock()) {  // Attempt to acquire the resource
        ptr2->greet();  // This will work if the resource is still valid
    } else {
        std::cout << "Resource has been deleted\n";
    }
 
    // Reset ptr1, which will delete the MyClass object
    ptr1.reset();
 
    // Try to lock the weak pointer again
    if (auto ptr2 = weakPtr.lock()) {
        ptr2->greet();
    } else {
        std::cout << "Resource has been deleted\n";
    }
 
    return 0;
}

Output:

C++ Source
MyClass Constructor
Hello from MyClass!
Resource has been deleted
MyClass Destructor

In this example:

  • weakPtr does not increase the reference count of the object, so it does not prevent the object from being deleted when ptr1 is reset.
  • The weakPtr.lock() function attempts to convert the weak_ptr into a shared_ptr. If the object has been deleted, it returns nullptr.

4. Benefits of Smart Pointers

  • Automatic Resource Management: Memory is automatically freed when the smart pointer goes out of scope.
  • Prevents Memory Leaks: Smart pointers ensure that dynamically allocated memory is properly deallocated, reducing the chances of memory leaks.
  • Avoids Dangling Pointers: When a smart pointer is destroyed, it automatically nullifies itself, preventing access to an invalid memory address.
  • Improved Code Safety: By managing ownership and lifecycle of dynamically allocated objects, smart pointers reduce the risk of common errors such as double-free or use-after-free.

5. Conclusion

Smart pointers in C++ provide a powerful mechanism to handle dynamic memory management safely and efficiently. By using unique_ptr, shared_ptr, and weak_ptr, C++ programmers can avoid many pitfalls associated with manual memory management, such as memory leaks, dangling pointers, and undefined behavior from multiple ownership. These smart pointers simplify resource management, improve code safety, and lead to cleaner, more maintainable C++ programs.

6.3. Lambda Expressions

Lambda expressions in C++ provide a way to define anonymous functions (i.e., functions without a name) inline within code. Introduced in C++11, they are especially useful for situations where a short function is needed, but defining a separate function would be cumbersome or unnecessary. Lambdas allow for the creation of functions that are local to a specific scope, often reducing the need for verbose code.

A lambda expression can capture variables from its surrounding scope, accept parameters, and return a value.

Syntax of Lambda Expression

The general syntax of a lambda expression in C++ is:

C++ Source
[ capture ] ( parameters ) -> return_type { body }
  • Capture List: [ ] captures variables from the surrounding scope that the lambda can access.
  • Parameters: ( ) defines the parameters that the lambda will take (like function parameters).
  • Return Type: -> return_type defines the return type of the lambda expression. This part is optional and will be inferred by the compiler if omitted.
  • Body: { } defines the code block that the lambda will execute when invoked.

Components of Lambda Expressions

  • Capture Clause [ ]: It defines which external variables the lambda can access.
    • []: Captures nothing (default).
    • [x]: Captures the variable x by value.
    • [&]: Captures all variables by reference.
    • [=]: Captures all variables by value.
    • [x, &y]: Captures x by value and y by reference.
  • Parameters ( ): Similar to function parameters. If there are no parameters, you can leave the parentheses empty.
  • Return Type ->: Optionally, you can specify the return type, especially when it cannot be deduced.
  • Body { }: The function body, where the code for the lambda's behavior is written.

Basic Example

Here's a basic example of a lambda expression in C++:

C++ Source
#include <iostream>
using namespace std;
 
int main() {
    // A simple lambda that adds two numbers
    auto add = [](int a, int b) -> int {
        return a + b;
    };
 
    cout << "Sum: " << add(5, 3) << endl;
    return 0;
}

Explanation:

  • auto add = [](int a, int b) -> int { return a + b; };: This lambda function takes two int parameters, adds them, and returns an int result.
  • The lambda is assigned to the variable add, and can be invoked just like a regular function.

Examples of Lambda Expressions with Different Features

1. Capturing Variables by Value

C++ Source
#include <iostream>
using namespace std;
 
int main() {
    int x = 10;
    int y = 20;
 
    // Capture x and y by value
    auto lambda1 = [x, y]() {
        cout << "Sum inside lambda1: " << x + y << endl;
    };
 
    lambda1(); // Outputs: Sum inside lambda1: 30
    return 0;
}

Explanation:

  • [x, y]: Captures x and y by value. The lambda gets copies of x and y, so changes to the original variables outside the lambda won't affect the captured values.

2. Capturing Variables by Reference

C++ Source
#include <iostream>
using namespace std;
 
int main() {
    int x = 10;
    int y = 20;
 
    // Capture x and y by reference
    auto lambda2 = [&x, &y]() {
        x += 5;
        y += 10;
        cout << "Sum inside lambda2: " << x + y << endl;
    };
 
    lambda2(); // Outputs: Sum inside lambda2: 45
    cout << "Updated x: " << x << ", y: " << y << endl;  // Outputs: Updated x: 15, y: 30
    return 0;
}

Explanation:

  • [&x, &y]: Captures x and y by reference, so the lambda can modify the original values of x and y outside the lambda.

3. Lambda with No Parameters and No Return Value

C++ Source
#include <iostream>
using namespace std;
 
int main() {
    // A simple lambda that prints a message
    auto printMessage = []() {
        cout << "Hello from Lambda!" << endl;
    };
 
    printMessage();  // Outputs: Hello from Lambda!
    return 0;
}

Explanation:

  • This lambda takes no parameters and does not return any value. It simply prints a message when invoked.

4. Lambda with Return Type Inference

C++ Source
#include <iostream>
using namespace std;
 
int main() {
    auto multiply = [](int a, int b) { return a * b; };
 
    cout << "Multiplication Result: " << multiply(4, 5) << endl; // Outputs: 20
    return 0;
}

Explanation:

  • The return type -> is omitted, and the compiler automatically infers it based on the return statement (int in this case).

5. Lambda with Explicit Return Type

C++ Source
#include <iostream>
using namespace std;
 
int main() {
    auto divide = [](int a, int b) -> double { return static_cast<double>(a) / b; };
 
    cout << "Division Result: " << divide(10, 3) << endl;  // Outputs: 3.33333
    return 0;
}

Explanation:

  • The return type is explicitly specified as double. The static_cast ensures that the division result is of type double, even though a and b are ints.

Advantages of Using Lambda Expressions

  • Concise Code: Lambdas allow you to define small functions inline without needing to declare a full function.
  • Access to Local Variables: Lambdas can capture variables from the enclosing scope, making them versatile.
  • Functional Programming Style: They facilitate functional programming paradigms such as higher-order functions (e.g., passing functions as arguments).
  • Efficient: Lambdas are often used with algorithms like std::sort, std::for_each, and other STL functions, leading to cleaner and more efficient code.

Use Case Example with STL Algorithm

C++ Source
#include <iostream>
#include <vector>
#include <algorithm>
 
using namespace std;
 
int main() {
    vector<int> numbers = {5, 3, 8, 1, 9, 2};
 
    // Use lambda with std::sort to sort in descending order
    sort(numbers.begin(), numbers.end(), [](int a, int b) -> bool {
        return a > b;  // Sort in descending order
    });
 
    // Print sorted numbers
    for (int num : numbers) {
        cout << num << " ";
    }
    // Output: 9 8 5 3 2 1
    return 0;
}

Explanation:

  • The lambda expression [](int a, int b) -> bool { return a > b; } is passed as a comparator to the std::sort function, allowing sorting in descending order.

Conclusion

Lambda expressions in C++ provide a powerful and flexible way to define inline functions. They offer concise syntax, the ability to capture variables from the surrounding scope, and can be used effectively with STL algorithms to make code more readable and efficient. Understanding how to use lambdas effectively is an essential skill for modern C++ programming.

6.4. Move semantics

It is an advanced feature introduced in C++11 to optimize the performance of programs by eliminating unnecessary copies of data. It allows for the efficient transfer of resources between objects without the need to perform expensive deep copies. Understanding move semantics is essential for writing high-performance C++ code, especially when working with large objects or containers.

Key Concepts:

  • Rvalue References: A fundamental component of move semantics is rvalue references, which are denoted by &&. Rvalue references allow you to distinguish between objects that can be moved from (rvalues) and objects that should be copied (lvalues).
    • Lvalue: An object that has a persistent location in memory (i.e., a named object).
    • Rvalue: An object that is temporary and does not have a persistent location in memory. It can be moved from (e.g., a result of an expression or a temporary object).
  • Move Constructor: A move constructor allows an object to take ownership of resources from another object instead of copying them. It transfers the data from a source object to the destination object and leaves the source in a valid but unspecified state.
  • Move Assignment Operator: The move assignment operator allows an existing object to take ownership of resources from another object, transferring data and leaving the source in a valid state.

Why Move Semantics?

In many cases, copying data from one object to another (especially large objects, like vectors, strings, or user-defined types) can be inefficient because it involves creating a new copy of the entire object. Move semantics allows you to "move" resources from one object to another instead of copying them, thus improving performance.

Key Operations:

  • Copying: Creates a new object and copies the contents from another object.
  • Moving: Transfers the ownership of data from one object to another, leaving the original object in a state where it no longer owns the data but is still valid.

Move Semantics in Action:

Let’s go through an example of how move semantics works in C++.

Example 1: Move Constructor

C++ Source
#include <iostream>
#include <vector>
 
class MyClass {
public:
    std::vector<int> data;
 
    MyClass(std::vector<int>&& input_data) : data(std::move(input_data)) {
        std::cout << "Move Constructor Called!" << std::endl;
    }
 
    void showData() {
        for (auto& el : data) {
            std::cout << el << " ";
        }
        std::cout << std::endl;
    }
};
 
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    MyClass obj1(std::move(vec));  // Move the vector into obj1
 
    obj1.showData();  // Displays: 1 2 3 4 5
 
    // The original vector 'vec' is now in a valid but unspecified state
    std::cout << "Size of vec after move: " << vec.size() << std::endl;  // Likely 0
}

Explanation:

  • std::move(vec) casts the vec (an lvalue) to an rvalue reference, enabling the move constructor of MyClass to be called.
  • The move constructor of MyClass takes std::vector<int>&& as a parameter and moves the contents of vec to the data member of obj1.
  • After the move, vec is in a valid state but its content is no longer reliable; it is often in a "moved-from" state where its size is usually zero.

Example 2: Move Assignment Operator

C++ Source
#include <iostream>
#include <vector>
 
class MyClass {
public:
    std::vector<int> data;
 
    MyClass(std::vector<int>&& input_data) : data(std::move(input_data)) {
        std::cout << "Move Constructor Called!" << std::endl;
    }
 
    MyClass& operator=(MyClass&& other) {
        std::cout << "Move Assignment Operator Called!" << std::endl;
        if (this != &other) {
            data = std::move(other.data);  // Move assignment
        }
        return *this;
    }
 
    void showData() {
        for (auto& el : data) {
            std::cout << el << " ";
        }
        std::cout << std::endl;
    }
};
 
int main() {
    std::vector<int> vec1 = {1, 2, 3, 4, 5};
    std::vector<int> vec2 = {10, 20, 30, 40, 50};
 
    MyClass obj1(std::move(vec1));  // Move constructor
    MyClass obj2(std::move(vec2));  // Move constructor
 
    obj1.showData();  // Displays: 1 2 3 4 5
    obj2.showData();  // Displays: 10 20 30 40 50
 
    obj1 = std::move(obj2);  // Move assignment
 
    obj1.showData();  // Displays: 10 20 30 40 50
    obj2.showData();  // Now in a valid but unspecified state
}

Explanation:

  • The move assignment operator allows obj1 to take ownership of obj2's data by moving the contents of obj2's data into obj1's data.
  • After the move assignment, obj2 is in a valid but unspecified state (its data may be empty or left in some other safe state).

When to Use Move Semantics?

Move semantics should be used when:

  • You are dealing with temporary objects that can be moved instead of copied.
  • You want to avoid the performance overhead of deep copying large data structures (such as containers).
  • You have objects that manage resources such as dynamic memory, file handles, or sockets, which can be moved between objects instead of copied.

Best Practices:

  • Return Value Optimization (RVO) and Named Return Value Optimization (NRVO): Modern compilers often optimize the return of objects to eliminate unnecessary copies, but using move semantics explicitly can still provide performance gains.
  • Always prefer std::move when you intend to move an object, especially when passing objects to functions or constructors.
  • Don’t move from objects that are still in use: After moving from an object, it's important that you don't accidentally access or modify it. Always ensure that moved-from objects are in a valid state.

Conclusion: Move semantics is a powerful feature in C++ that allows efficient transfer of resources from one object to another, avoiding unnecessary copying. By using rvalue references, move constructors, and move assignment operators, you can write more efficient and performant C++ code, especially for large or complex objects.

ADVERTISEMENT
Responsive Ad Unit • Software Engineering & High-Performance Systems
πŸ“ Concept Mastery Assessment
12 Questions Score: 0 / 0

Select your answer for each question below to receive immediate correctness evaluation and in-depth technical rationale.

Question 1 of 12
What does the RAII (Resource Acquisition Is Initialization) design pattern dictate in C++?
Explanation
RAII ties resource ownership to object lifetime. When the object goes out of scope, its destructor automatically releases the resource, preventing leaks.
Question 2 of 12
Which modern C++ smart pointer represents exclusive, non-copyable ownership of a dynamically allocated heap object?
Explanation
std::unique_ptr provides sole ownership of a resource. It cannot be copied, only moved (via std::move), with zero runtime overhead over a raw pointer.
Question 3 of 12
Which smart pointer uses atomic reference counting to allow multiple shared owners of a single object?
Explanation
std::shared_ptr maintains a shared reference count. When the last shared_ptr pointing to the resource is destroyed, the managed object is deleted.
Question 4 of 12
What issue does 'std::weak_ptr' solve when using 'std::shared_ptr'?
Explanation
Cyclic shared_ptr relationships prevent reference counts from ever reaching 0, causing permanent memory leaks. std::weak_ptr observes without owning.
Question 5 of 12
What is the preferred, exception-safe function to create a std::unique_ptr in C++14+?
Explanation
std::make_unique<T>() avoids explicit 'new', is exception-safe, and prevents potential leaks if other argument evaluations throw.
Question 6 of 12
What does move semantics (introduced in C++11) achieve?
Explanation
Move semantics 'steals' heap pointers and resources from temporary rvalues (using rvalue references T&&), replacing slow deep copies with fast pointer swaps.
Question 7 of 12
What does 'std::move(x)' actually do in C++?
Explanation
std::move produces no machine code at runtime; it is a compile-time cast to an rvalue reference, informing compilers that x can be moved from.
Question 8 of 12
What is the 'Rule of Five' in modern C++?
Explanation
Classes managing raw resources should explicitly declare or delete the big five: Destructor, Copy Constructor, Copy Assignment, Move Constructor, and Move Assignment.
Question 9 of 12
What does an rvalue reference syntax look like in C++11?
Explanation
Double ampersand 'Type&&' denotes an rvalue reference, binding to temporary objects or expressions cast via std::move.
Question 10 of 12
Which standard library header provides multi-threading primitives like 'std::thread' in modern C++?
Explanation
<thread> provides cross-platform standard thread management (std::thread, std::jthread in C++20).
Question 11 of 12
What happens if a std::thread object is destroyed while still joinable (without calling .join() or .detach())?
Explanation
Destructing a joinable std::thread calls std::terminate(). C++20 introduced std::jthread which automatically joins on destruction.
Question 12 of 12
What synchronization primitive in <mutex> prevents concurrent race conditions by ensuring exclusive access to shared data?
Explanation
std::mutex guarantees mutual exclusion. RAII wrappers like std::lock_guard lock upon construction and unlock upon destruction.
0 / 0 (0%)
Great job! Review the questions above and proceed when ready.
ADVERTISEMENT
Responsive Ad Unit • Technical Courses & Certifications